/* lcnd/lcnd.c 
 * 
 * This file is part of lcnd. 
 * 
 * lcnd is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version. 
 * 
 * lcnd is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with lcnd. If not, see <https://www.gnu.org/licenses/>
 */ 




#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <dirent.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/mman.h>
#include <sys/sendfile.h>

#include <lcnd/lcnd.h>
#include <lcnd/lcn.h>


/* it does look like vccd! */

int do_send_file(int fd, char *req);
int do_list_articles(int fd, char *req);

struct klist_node 	connections = KLIST_NODE_INIT(&connections);
int 			narticles;
struct klist_node 	articles = KLIST_NODE_INIT(&articles);
DIR 			*dir;

struct reqtype {
	char 	*name;
	int 	(* do_request) (int fd, char *req);
} reqtypes[] = {
	{ "read", do_send_file }, 
	{ "list", do_list_articles }, 
	{ NULL, NULL }
};

int lcnd_init(void) {
	struct article *a;
	struct dirent *dr;

	narticles = 0;

	if (unlikely(!(dir = opendir(LCND_PATH)))) {
		perror("opendir");

		abort();
	}

	while ((dr = readdir(dir))) {
		if (dr->d_type != DT_REG) 
			continue;

		if (unlikely(!(a = malloc(sizeof(struct article))))) {
			fprintf(stderr, "*** malloc() failed\n");

			continue;
		}

		klist_init(&a->node);
		a->name = strdup(dr->d_name);
		a->len = strlen(a->name) + 1;

		klist_add(&articles, &a->node);
		narticles++;

		continue;
	}

	printf("%d articles. \n", narticles);
	return 0;
}


int lcnd_new_connection(int fd) {
	struct connection *c;

	if (unlikely(!(c = amalloc(sizeof(struct connection))))) {
		lcnd_log("*** malloc() failed\n");

		return 1;
	}

	connection_init(c);

	c->fd = fd;
	klist_init(&c->node);

	klist_add(&connections, &c->node);
	lcnd_log("New connection #%d. \n", fd);

	return 0;
}

struct connection *find_connection(int fd) {
	struct connection *c;
	struct klist_node *n;

	for (n = connections.next; n != &connections; n = n->next) {
		c = container_of(n, struct connection, node);

		if (c->fd == fd) 
			return c;
	}

	lcnd_log("*** connection not found #%d. \n", fd);

	return NULL;
}


int lcnd_close_connection(int fd) {
	free(find_connection(fd));
	lcnd_log("Client closed #%d. \n", fd);

	return 0;
}


int do_list_articles(int fd, char *req) {
	struct klist_node *n;
	struct article *a;
	char buf[9];

	(void) req;

	sprintf(buf, "%08d", narticles);
	write(fd, buf, 8);

	for (n = articles.next; n != &articles; n = n->next) {
		a = container_of(n, struct article, node);
		write(fd, a->name, a->len);
	}

	return 0;
}


int do_send_file(int fd, char *req) {
	int 		in_fd;
	size_t 		size;
	char 		buf[16];

	if (unlikely((in_fd = open(req + 4, O_RDONLY)) < 0)) {
		write(fd, "E404", 4);

		return 0;
	}

	write(fd, "E200", 4);

	size = lseek(in_fd, 0, SEEK_END);
	sprintf(buf, "%08ld", size);
	write(fd, buf, 8);

	lseek(in_fd, 0, SEEK_SET);
	sendfile(fd, in_fd, NULL, size);

	close(in_fd);

	return 0;
}


int do_invalid_request(int fd, char *req) {
	(void) req;

	write(fd, "E400", 4);
	lcnd_log("invalid request from #%d\n", fd);

	return 0;
}


int lcnd_do_request(int fd, char *req) {
	struct reqtype *p;

	for (p = reqtypes; p->name; p++) 
		if (!strncmp(p->name, req, 4)) 
			return p->do_request(fd, req);

	return do_invalid_request(fd, req);
}

